#include <iostream>
#include <cstdlib>
#include <IL/il.h>
using namespace std;

#define BLOCK 16 //définit un bloc de thread de 16x16

struct complex {
  float r;
  float i;

  __device__ complex(float r, float i) : r(r), i(i) {}
  __device__ float magnitude() {return r*r + i*i;}
  __device__ complex operator*(const complex& c) {
    return complex(r * c.r - i * c.i, i * c.r + r * c.i);
  }

  __device__ complex operator+(const complex& c) {
    return complex(r + c.r, i + c.i);
  }  

};

__device__ int julia(int x, int y, int size) {

  const float scale = 1.5;

  float jx = scale * (float)(size/2 - x)/(size/2);
  float jy = scale * (float)(size/2 - y)/(size/2);

  complex c(-0.8, 0.156);
  complex a(jx, jy);

  for(unsigned int i = 0 ; i < 200 ; ++i) {
 
    a = a * a + c;
    if(a.magnitude() > 1000) {
      return 0;
    }

  }

  return 1;

}

__global__ void kernel(unsigned char* image, int size) {
  //on se place dans l'image
  int i = blockIdx.x * blockDim.x + threadIdx.x;
  int j = blockIdx.y * blockDim.y + threadIdx.y ;

  //on calcule la case correspondante
  unsigned int offset = (j*size + i) * 4;

  //on la traite
  image[offset] = 0;
  image[offset+1] = 0;
  image[offset+2] = 255*julia(i, j, size);
  image[offset+3] = 255;
}

int main(int argc, char* argv[]) {

  cudaEvent_t start, stop0, stop1;
  float time;

  cudaEventCreate(&start);
  cudaEventCreate(&stop0);
  cudaEventCreate(&stop1);
  
  //calcul d'une taille traitable par notre structure
  unsigned int size = static_cast<unsigned int>(atoi(argv[1]));
  size = ((size/BLOCK)+(size%BLOCK != 0))*BLOCK;
  
  //allocation
  unsigned char* image = new unsigned char[size*size*4];
  unsigned char* dev_image;

  cudaEventRecord(start, 0);
  cudaMalloc(&dev_image, size*size*4);

  //calcul de la grille de blocs
  dim3 grid(size/BLOCK, size/BLOCK);
  //dimensions d'un bloc
  dim3 block(BLOCK, BLOCK);
  //lancement du kernel
  kernel<<<grid,block>>>(dev_image, size);

  cudaEventRecord(stop0, 0);
  cudaEventSynchronize(stop0);
  //récupération des données
  cudaMemcpy(image, dev_image, size*size*4, cudaMemcpyDeviceToHost);
  cudaEventRecord(stop1, 0);
  cudaEventSynchronize(stop1);

  cudaEventElapsedTime(&time, start, stop0);
  cout << "time kernel=" << time << endl;

  cudaEventElapsedTime(&time, stop0, stop1);
  cout << "time copy=" << time << endl;

  cudaEventDestroy(start);
  cudaEventDestroy(stop0);
  cudaEventDestroy(stop1);

  unsigned int handle;

  //liberation GPU
  cudaFree(dev_image);

  //écriture d'image
  ilInit();

  ilGenImages(1, &handle);
  ilBindImage(handle);

  ilTexImage(size, size, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, image);

  ilSetData(image);
  ilEnable(IL_FILE_OVERWRITE);
  ilSaveImage("out-cuda.png");
  ilDeleteImages(1, &handle);

  delete [] image;

  return 0;

}
